package vip.zhenzicheng.nio.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.Set;

/**
 * NIO 通信客户端处理器
 *
 * @author zhenzicheng
 * @date: 2022-06-01 18:45
 */
public class NioClientHandle implements Runnable {
  private final String host;
  private final int port;
  private final Selector selector;
  private final SocketChannel socketChannel;
  private volatile boolean started;

  public NioClientHandle(String ip, int port) {
    this.host = ip;
    this.port = port;

    try {
      // 创建选择器的实例
      selector = Selector.open();
      // 创建ServerSocketChannel实例
      socketChannel = SocketChannel.open();
      // 设置通道为非阻塞模式
      socketChannel.configureBlocking(false);

      started = true;
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  public void stop() {
    this.started = false;
  }


  @Override
  public void run() {
    try {
      doConnect();
    } catch (IOException e) {
      e.printStackTrace();
      System.exit(1);
    }
    // 循环遍历selector
    while (started) {
      // 无论是否有读写事件发生，selector每隔1s被唤醒一次
      try {
        selector.select(1000);
        // 获取当前有哪些事件可以使用
        Set<SelectionKey> keys = selector.selectedKeys();
        // 转换为迭代器
        Iterator<SelectionKey> it = keys.iterator();
        SelectionKey key;
        while (it.hasNext()) {
          key = it.next();
          // 必须先删除处理过的 SelectionKey，否则它仍然处于被激活状态可能会再次尝试处理它
          it.remove();
          try {
            handleInput(key);
          } catch (Exception e) {
            if (key != null) {
              key.cancel();
              if (key.channel() != null) {
                key.channel().close();
              }
            }
          }
        }
      } catch (Exception e) {
        e.printStackTrace();
        System.exit(1);
      }
    }

    // selector 关闭后会自动释放里面管理的资源
    if (selector != null) {
      try {
        selector.close();
      } catch (IOException e) {
        e.printStackTrace();
      }
    }

  }

  // 具体的时间处理方法
  private void handleInput(SelectionKey key) throws IOException {
    if (key.isValid()) {
      // 获得关心当前事件的channel
      SocketChannel sc = (SocketChannel) key.channel();
      // 连接事件
      if (key.isConnectable()) {
        if (sc.finishConnect()) {
          socketChannel.register(selector, SelectionKey.OP_READ);
        } else {
          System.exit(1);
        }
      }

      // 有数据可读事件
      if (key.isReadable()) {
        // 创建ByteBuffer并开辟1M的缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        // 读取请求码流，返回读到的字节数
        int readBytes = sc.read(buffer);
        // 读取到字节，对字节进行编码
        if (readBytes > 0) {
          // 将缓冲区当前limit设置为position,position=0 用于读取后续对缓冲区的读操作
          buffer.flip(); // 切换为读模式
          byte[] bytes = new byte[buffer.remaining()]; // 获取 limit - pos 的长度
          // 将缓冲区可读字节数组复制到新数组中
          buffer.get(bytes);

          String result = new String(bytes, StandardCharsets.UTF_8);
          System.out.println("客户端收到消息：" + result);
        } else if (readBytes < 0) {
          key.channel();
          sc.close();
        }
      }
    }
  }

  private void doWrite(SocketChannel channel, String request) throws IOException {
    // 将消息编码为字节数组
    byte[] bytes = request.getBytes();
    ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
    // 将字节数组复制到缓冲区
    writeBuffer.put(bytes);
    writeBuffer.flip();
    // 发送缓冲区的字节数组 且关心事件和读写网络并不冲突
    channel.write(writeBuffer);
  }

  private void doConnect() throws IOException {
    // 非阻塞连接
    if (socketChannel.connect(new InetSocketAddress(host, port))) {
      socketChannel.register(selector, SelectionKey.OP_READ);
    } else {
      socketChannel.register(selector, SelectionKey.OP_CONNECT);
    }
  }

  /**
   * 对外暴露的发送信息API
   */
  public void sendMsg(String msg) throws IOException {
    doWrite(socketChannel, msg);
  }

}
